package org.sadko.boozemeter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ListIterator;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.TextView;

public class Calibrator extends Activity implements SensorListener, Runnable {
	public static final long MEASUREMENT_TIME_SEC = 5;
	
	public static final int MESSAGE_STOP_MEASUREMENT = 0;
	public static final int MESSAGE_RECIEVE_RESULTS = 1;
	public static final int MESSAGE_EXIT = 2;
		
	public static final float[] GRAVITY = {0, 0, -SensorManager.GRAVITY_EARTH};
	
	public static final float EPS_DISP_MIN = (float) 0.2;
	public static final float EPS_A_MAX = (float) 2.5;
	public static final float EPS_A_MIN = (float) 0.4;
	
	public static MyHandler mHndl;
	private SensorManager mSensorManager;
	
	private float mYaw = 0, mPitch = 0, mRoll = 0;
	private float[] mCorr = new float[3];
	private float[] mCurG = new float[3];
	private boolean mOrientUpToDate = false;
	private boolean mAccessAllowed = true;  //to ensure we won't try to modify accels simultaneously

	private FileWriter fw;
	
	private long progress = 0;
	private long mLastChangeTime;
	private long mInitTime;
	private ProgressBar mProgBar;
	private TextView mTv;

	
	private ArrayList<Float> mAccX = new ArrayList();
	private ArrayList<Float> mAccY = new ArrayList();
	private ArrayList<Float> mAccZ = new ArrayList();
	
	
	private class MyHandler extends Handler{
		@Override
		public void handleMessage(Message msg){
			switch(msg.what){
			case MESSAGE_STOP_MEASUREMENT:{
				mSensorManager.unregisterListener(Calibrator.this);
				Thread thread = new Thread(Calibrator.this);
				thread.start();
				break;
			}
			case MESSAGE_RECIEVE_RESULTS:{
				
				Dialog dialog = new AlertDialog.Builder(Calibrator.this
						).setIcon(org.sadko.boozemeter.R.drawable.icon_drunk
						).setTitle("Success!"
						).setMessage(org.sadko.boozemeter.R.string.calibration_successfull_dialog
						).setPositiveButton("Ok", new OnClickListener(){
							public void onClick(DialogInterface dialog,int which) {
								Calibrator.this.finish();
							}
						}
						).create();
				dialog.show();
				
				
				break;	
			}
			}
		}

	}
	
	private void updateUI() {
		mTv.setText(String.valueOf(MEASUREMENT_TIME_SEC-(++progress)) + " seconds left");
		mProgBar.setProgress((int)((MEASUREMENT_TIME_SEC-progress)*100/(float)MEASUREMENT_TIME_SEC));
		
	}
	
	@Override
	public void onCreate (Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		this.setContentView(org.sadko.boozemeter.R.layout.calibrator);
		//TODO: Create fancy layout for calibrator
		mHndl = new MyHandler();
		mOrientUpToDate = false;
		
		try {
		    File root = new File("/data/data/org.sadko.boozemeter/");
		    if (root.canWrite()){
		        File calibration = new File(root, "calibration.bzm");
		        calibration.delete();
		        calibration.createNewFile();
		        fw = new FileWriter(calibration);
		    }
		} catch (IOException e) {
		    Log.e("FileWriter", "Could not write file " + e.getMessage());
		}
		
		mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); //USE for actual device
	    
	    mProgBar = (ProgressBar) findViewById(R.id.prog_bar);
	    
		mProgBar.setProgress((int)100);
	    
		mTv = (TextView) findViewById(org.sadko.boozemeter.R.id.time_remain);
 	    mTv.setText(String.valueOf(MEASUREMENT_TIME_SEC-progress) + " seconds left");
	  
 	 //TODO: Don't forget to uncomment sensormanagers!
	  	//List <Sensor> s = mSensorManager.getSensorList(SensorManager.SENSOR_ACCELEROMETER);
	  	//mSensorManager.registerListener(Calibrator.this, s.get(0), SensorManager.SENSOR_DELAY_FASTEST);
	  	//s = mSensorManager.getSensorList(SensorManager.SENSOR_ORIENTATION);
	  	//mSensorManager.registerListener(Calibrator.this, s.get(0), SensorManager.SENSOR_DELAY_FASTEST);
	  	mSensorManager.registerListener(Calibrator.this, SensorManager.SENSOR_ORIENTATION | 
	  			SensorManager.SENSOR_ACCELEROMETER,SensorManager.SENSOR_DELAY_FASTEST);
		Message msg = Message.obtain();
		msg.what = MESSAGE_STOP_MEASUREMENT;
		mHndl.sendMessageDelayed(msg, MEASUREMENT_TIME_SEC*1000);
		mLastChangeTime = System.currentTimeMillis();
		mInitTime = System.currentTimeMillis();
	}
	
	public void run(){
		refineAccels();
		try {
			fw.write(String.valueOf(mCorr[0])+"\n"+String.valueOf(mCorr[1])+"\n"+String.valueOf(mCorr[2]));
		} catch (IOException e) {
			e.printStackTrace();
		}
		Message msg = Message.obtain();
		msg.what = MESSAGE_RECIEVE_RESULTS;
		mHndl.sendMessage(msg);
	}
	
	public void onPause(){
		mSensorManager.unregisterListener(this);
		mHndl.removeMessages(MESSAGE_STOP_MEASUREMENT);
		try {
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}	
		super.onPause();
		finish();
		
	}
	
	@Override
	public void onDestroy(){
		mSensorManager.unregisterListener(this);
		mOrientUpToDate = false;
		try {
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}				
		super.onDestroy();
	}

	public void onAccuracyChanged(int sensor, int accuracy) {
		
	}

	
	public void onSensorChanged(int sensor, float[] values) {
		switch(sensor){
		case SensorManager.SENSOR_ACCELEROMETER:{
			if(!mOrientUpToDate || !mAccessAllowed) return;
			if(Math.abs(mInitTime - System.currentTimeMillis()) < 500) return;
			mAccessAllowed = false;
			for(int i=0; i < 3; i++)
				mCurG[i] = values[i];
			mCurG = updateG(mYaw, mPitch, mRoll, mCurG);
			mAccX.add(mCurG[0]);// - mCurG[0]);
			mAccY.add(mCurG[1]);// - mCurG[1]);
			mAccZ.add(mCurG[2]);// - mCurG[2]);
			mAccessAllowed = true;
			if(Math.abs(mLastChangeTime - System.currentTimeMillis())>1000){
				updateUI();
				mLastChangeTime = System.currentTimeMillis();
			}
			break;
		}
		case SensorManager.SENSOR_ORIENTATION:{
			mYaw   = -values[0];
			mPitch = -values[1];
			mRoll  = -values[2];
			//updateG(mYaw, mPitch, mRoll);
			mOrientUpToDate = true;
			break;
		}
		}
	}
	
	private void refineAccels(){
		while(!mAccessAllowed){} //while we modify mAcc's by getting new data, we don't want to intervene - so do nothing
		mAccessAllowed = false;
		ListIterator<Float> itrX = mAccX.listIterator();
		ListIterator<Float> itrY = mAccY.listIterator();
		ListIterator<Float> itrZ = mAccZ.listIterator();
		float[] mean = new float[3];
		int size = mAccZ.size(); //overinsuring,that there won't be simultaneous reading from Lists
		if (size == 0) return; 
		for(int i = 0; i < size; i++){
			mean[0] += (Float)itrX.next();
			mean[1] += (Float)itrY.next();
			mean[2] += (Float)itrZ.next();
		}
		mCorr[0] = mean[0]/size;
		mCorr[1] = mean[1]/size;
		mCorr[2] = mean[2]/size;
	}
	
	private float[] vectMatrMult(float[] in, float[][] rot){
		float[] out = new float[3];
		
		for(int i = 0; i<3; i++)
			for(int j = 0; j<3; j++){
				out[i] += rot[i][j]*in[j];
			}
		return out;
	}
	
	private float[] updateG(float yaw, float pitch, float roll, float[] in){
		float[] out = new float[3]; 
		float[][] rotX = new float [3][3];
		float[][] rotY = new float [3][3];
		for(int i = 0; i<3; i++)
			for(int j = 0; j<3; j++){
				rotX[i][j] = 0;
				rotY[i][j] = 0;
			}
		
		float sine = (float) Math.sin(pitch*Math.PI/180.0);
		float cosine = (float) Math.cos(pitch*Math.PI/180.0);
		rotX[0][0] = 1;
		rotX[1][1] = cosine;
		rotX[1][2] = -sine;
		rotX[2][1] = sine;
		rotX[2][2] = cosine;
		out = vectMatrMult(in, rotX);
		
		sine = (float) Math.sin(roll*Math.PI/180.0);
		cosine = (float) Math.cos(roll*Math.PI/180.0);
		rotY[1][1] = 1;
		rotY[0][0] = cosine;
		rotY[0][2] = -sine;
		rotY[2][0] = sine;
		rotY[2][2] = cosine;
		out = vectMatrMult(out, rotY);
		
		return out; //Here it's the G, which the device would actually show once on completely flat horizonatal surface
		//for(int i = 0; i < 3; i++)
		//	mCurG[i] = out[i];   
		
	}
}
